/**
 * 
 */
package it.polimi.cg16.controller;

import it.polimi.cg16.exceptions.InactivePlayerException;
import it.polimi.cg16.model.GameBoard;
import it.polimi.cg16.model.Player;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This class implements the controller part that manage the cycle of game and
 * turn of each player
 * 
 * @author Michele
 * 
 */
public class Game implements Runnable {
    private static final Logger LOGGER = Logger.getGlobal();
    private static final String CONTEXT = "context";

    static final int MAX_MOVES = 3;
    static final int MAX_PLAYER = 4;
    private static final int ERROR = -1;
    private static final int OK = 0;

    private List<PlayerController> playerController;
    private GameBoard gameBoard;
    private int starterPlayer;
    private int currentPlayer;
    private int nextPlayer;
    private List<Move> availableMove;

    public Game(List<PlayerController> listOfPlayer) {

        playerController = new ArrayList<>();
        playerController.addAll(listOfPlayer);
        gameBoard = new GameBoard(listOfPlayer.size());
        gameBoard.giveCard();
        associatePlayer(gameBoard.getPlayer());
    }

    /**
     * Calls for each player the initial update method
     */
    public void initialUpdate() {
        for (PlayerController pc : playerController) {
            pc.initialUpdate();
        }
    }

    /**
     * Calls for each player the update method
     */
    public void update() {
        for (PlayerController pc : playerController) {
            if (pc.isActive()) {
                pc.update();
            }
        }
    }

    /**
     * To set the starter position of the shepherds
     * 
     * @throws InterruptedException
     */
    private void firstSetShepherd() throws InterruptedException {
        if (playerController.size() > 2) {
            for (PlayerController pc : playerController) {
                while (pc.isActive()) {
                    try {
                        if (gameBoard.setShepherd(pc.getPlayer(), 0, pc.chooseCell())) {
                            // should be only updateCell
                            initialUpdate();
                            pc.endTurn();
                            break;
                        }
                    } catch (InactivePlayerException e) {
                        LOGGER.log(Level.FINE, CONTEXT, e);
                        endGame(ERROR);
                    }
                }
            }
        } else {
            for (int shepherd = 0; shepherd < 2; shepherd++) {
                for (PlayerController pc : playerController) {
                    while (pc.isActive()) {
                        try {
                            if (gameBoard.setShepherd(pc.getPlayer(), shepherd, pc.chooseCell())) {
                                // should be only updateCell
                                initialUpdate();
                                pc.endTurn();
                                break;
                            }
                        } catch (InactivePlayerException e) {
                            LOGGER.log(Level.FINE, CONTEXT, e);
                            endGame(ERROR);
                        }
                    }
                }
            }
        }
    }

    /**
     * Control the cycle of the game
     * 
     * @throws InterruptedException
     */
    private void startGame() throws InterruptedException {
        initialUpdate();
        // Each shepherd select the starter cell
        firstSetShepherd();
        // Choose the starter player
        Random random = new Random();
        starterPlayer = random.nextInt(playerController.size());
        currentPlayer = starterPlayer;
        // Cycle of game
        update();
        while (currentPlayer >= 0) {
            if (playerController.get(currentPlayer).isActive()) {
                turn(playerController.get(currentPlayer));
            } else if (noActivePlayers()) {
                endGame(ERROR);
            }
            nextPlayer = currentPlayer;
            nextPlayer++;
            currentPlayer = (nextPlayer) % (playerController.size());
            // Control if the game is finished
            if (currentPlayer == starterPlayer && gameBoard.getFences() == 0) {
                currentPlayer = -1;
            }
            update();
        }
        // Calculate points
        gameBoard.calculatePoints();
        // Show all the results
        endGame(OK);
    }

    /**
     * Control the turn of single player
     * 
     * @param playerController
     * @param chosenShepherd
     * @throws InactivePlayerException
     */
    private void turn(PlayerController playerController) {
        int move = MAX_MOVES;
        int chosenShepherd;
        boolean shepherdMoved = false;

        try {
            /* If there are more than 2 players */
            if (this.playerController.size() > 2) {
                chosenShepherd = 0;
            } else { /* If there are only 2 players */
                chosenShepherd = playerController.chooseShepherd();
            }
            availableMove = new LinkedList<Move>();
            // Initializing the available moves list
            availableMove = initAvailableMoves();
            // Moving the blacksheep
            gameBoard.moveBlackSheep();
            while (move > 0) {
                // if possible updateRegions
                update();
                // Show available moves
                Move chosenMove = playerController.chooseMove(availableMove);
                if (chosenMove.doMove(gameBoard, playerController, chosenShepherd)) {
                    availableMove.remove(chosenMove);
                    // If i do a moveShepherd i can do all other moves
                    if (chosenMove.getType() == Move.SHEPHERD.getType()) {
                        shepherdMoved = true;
                        availableMove = new LinkedList<Move>();
                        // Re-initializing the available moves list
                        availableMove = initAvailableMoves();
                    }
                    move--;
                    if (move == 1 && !shepherdMoved) {
                        availableMove.clear();
                        availableMove.add(Move.SHEPHERD);

                    }
                }
            }
            // Moving the wolf
            gameBoard.moveWolf();
            playerController.endTurn();
            // return true;
        } catch (InactivePlayerException e) {
            LOGGER.log(Level.FINE, CONTEXT, e);
            e.setPlayer(playerController);
            playerController.setActive(false);
            // return false;
        }
    }

    /**
     * Initialize the available moves for a player with all the moves
     * 
     * @return a list of available moves
     */
    private List<Move> initAvailableMoves() {
        // Initializing the available moves list
        for (Move m : Move.values()) {
            availableMove.add(m);
        }
        return availableMove;
    }

    /**
     * Associate each player controller to its physical player
     * 
     * @param listOfPlayers
     */
    private void associatePlayer(Player[] listOfPlayers) {
        for (int i = 0; i < playerController.size(); i++) {
            playerController.get(i).setPlayer(listOfPlayers[i]);
            playerController.get(i).setGame(this);
        }
    }

    public GameBoard getGameBoard() {
        return gameBoard;
    }

    public void run() {
        try {
            startGame();
        } catch (InterruptedException e) {
            LOGGER.log(Level.FINE, CONTEXT, e);
        }
    }

    /**
     * End the game. If the game has been terminated because of an error it
     * sends a bye message to all client, else it shows to all client the
     * results. Finally it interrupts the game thread
     * 
     * @param status
     * @throws InterruptedException
     */
    private void endGame(int status) throws InterruptedException {
        for (PlayerController pc : playerController) {
            if (status == 0) {
                // Show all the results
                // End game to players
                pc.endGame();
            } else {
                // Send a bye message to close other clients
                pc.bye();
            }
        }

        throw new InterruptedException();
    }

    /**
     * Verifies if all players are inactive
     * 
     * @return true if there are no more active players, false otherwise
     */
    private boolean noActivePlayers() {
        for (PlayerController pc : playerController) {
            if (pc.isActive()) {
                return false;
            }
        }
        return true;
    }

    /**
     * 
     * @return a list of all playerController of the game
     */
    public List<PlayerController> getPlayerController() {
        return playerController;
    }
}
